﻿using psafth.AMB;
using psafth.RCTiming.Common;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;

namespace psafth.RCTiming.Main.Pages
{
    /// <summary>
    /// Interaction logic for PracticePage.xaml
    /// </summary>
    public partial class PracticePage : Page, INotifyPropertyChanged
    {
        #region Private declarations

        private AMBCommunicator _decoderCommunicator;
        private int _decoderNoise = 0;
        private double _decoderVoltage;
        private double _decoderTemperature;
        private TimeSpan _decoderDifference;

        private DispatcherTimer _dispatcherTimer;
        private DispatcherTimer _clockTimer;

        private DateTime? _startDateTime;
        private DateTime? _stopDateTime;

        private bool _isDecoderClockSynchronized = false;

        #endregion

        #region On property changed
        [field: NonSerialized]
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string name)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(name));
            }
        }
        #endregion

        #region Public properties

        /// <summary>
        /// The title of the practice round
        /// Used while displaying and reporting
        /// </summary>
        public string Title
        {
            get;
            set;
        }

        /// <summary>
        /// The duration of this practice round
        /// </summary>
        public TimeSpan Duration
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the time elapsed
        /// </summary>
        public TimeSpan TimeElapsed
        {
            get;
            set;
        }

        /// <summary>
        /// This pages parent window
        /// </summary>
        private MainWindow ParentMainWindow
        {
            get
            {
                return Window.GetWindow(this) as MainWindow;
            }
        }

        /// <summary>
        /// The decoders DateTime if the difference is set.
        /// </summary>
        private DateTime DecoderTime
        {
            get
            {
                return DateTime.Now.Add(_decoderDifference);
            }
        }

        /// <summary>
        /// Returns the decoders voltage
        /// </summary>
        public double DecoderVoltage
        {
            get
            {
                return _decoderVoltage;
            }
            set
            {
                _decoderVoltage = value;
                OnPropertyChanged("DecoderVoltage");
            }
        }

        /// <summary>
        /// Returns the decoders temperature
        /// </summary>
        public double DecoderTemperature
        {
            get
            {
                return _decoderTemperature;
            }
            set
            {
                this._decoderTemperature = value;
                OnPropertyChanged("DecoderTemperature");
            }
        }

        /// <summary>
        /// Returns the decoders noise
        /// </summary>
        public int DecoderNoise
        {
            get
            {
                return _decoderNoise;
            }
            set
            {
                _decoderNoise = value;
                pbNoise.ToolTip = string.Format("{0} dB", _decoderNoise);

                if (_decoderNoise > pbNoise.Maximum)
                    pbNoise.Maximum = _decoderNoise;

                OnPropertyChanged("DecoderNoise");
                OnPropertyChanged("PbNoiseForeGround");
            }
        }


        /// <summary>
        /// Returns the foreground color of the decoder noise progressbar to
        /// </summary>
        public SolidColorBrush ProgressBarNoiseForeGround
        {
            get
            {
                if (_decoderNoise <= 40)
                    return Brushes.Green;

                if (_decoderNoise >= 41)
                    return Brushes.Orange;

                if (_decoderNoise >= 70)
                    return Brushes.OrangeRed;

                if (_decoderNoise >= 100)
                    return Brushes.Red;

                return Brushes.Green;
            }
        }

        /// <summary>
        /// Gets or sets the competitors
        /// </summary>
        public ObservableCollectionExt<HeatCompetitor> Practisers
        {
            get;
            set;
        }

        #endregion

        public PracticePage()
        {
            InitializeComponent();

            this.DataContext = this;
            this.Practisers = new ObservableCollectionExt<HeatCompetitor>();

            this.Loaded += PracticePage_Loaded;
            this.Unloaded += PracticePage_Unloaded;
        }

        /// <summary>
        /// Calculates how long time has been since the practice was started.
        /// Updates the TimeElapsed property
        /// </summary>
        void UpdateTimeElapsed()
        {
            // if the start date time has been set, otherwise let it be
            if (this._startDateTime.HasValue)
                this.TimeElapsed = DateTime.Now.Subtract(this._startDateTime.Value);

            if (_startDateTime.HasValue && _stopDateTime.HasValue)
            {
                TimeSpan o = _stopDateTime.Value - _startDateTime.Value.Add(TimeElapsed);

                TimeSpan n = new TimeSpan(0);

                if (o <= n)
                    lblTimer.Foreground = Brushes.Red;

                lblTimer.Content = o.ToString(@"mm\:ss\.fff");
            }
        }

        /// <summary>
        /// Event for every tick of the clock timer. Once every second.
        /// Updates the clock time label.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void _clockTimer_Tick(object sender, EventArgs e)
        {
            lblComputerTime.Text = DateTime.Now.ToString(@"HH:mm:ss");
        }

        /// <summary>
        /// Event for every tick of the dispatch timer. Once every 100th of a second.
        /// Updates the time elapsed property.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void _dispatcherTimer_Tick(object sender, EventArgs e)
        {
            UpdateTimeElapsed();
        }

        /// <summary>
        /// 
        /// </summary>
        void RunRace()
        {
            if (_dispatcherTimer.IsEnabled)
                return;

            //if (IsRaceFinished)
            //    return;

            //if (IsFinished)
            //    return;

            if (!_isDecoderClockSynchronized)
                return;

            _startDateTime = DateTime.Now.Subtract(TimeElapsed);
            _stopDateTime = _startDateTime.Value.Add(Duration);

            _dispatcherTimer.IsEnabled = true;

            WriteToConsole("Race startar!");

            btnPause.IsEnabled = true;
            btnStop.IsEnabled = false;

            SoundPlayer.PlaySound(1, 1000, true);

            // TODO: Send to multicast thread
            //_mainWindow.SendToMulticastThread(new StartCommand() { HeatDurationLeft = new TimeSpan(0, HeatDuration, 0).Subtract(TimeElapsed) }, MulticastDataType.Start);

        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void _decoderCommunicator_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        async void _decoderCommunicator_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            // Check that something has been sent to this thread
            if (e.UserState == null)
                return;

            // If ProgressPercentage is equal to 1 the CRC check yeilded a false result.
            if (e.ProgressPercentage.Equals(1))
                WriteToConsole("Information: Mottog meddelande vars CRC ej kunde valideras");

            try
            {
                // Cast the e.UserState as a AMB transfer object.
                AMB.TransferObject returnObj = e.UserState as AMB.TransferObject;

                // Determine what kind of message type the object represent
                switch (returnObj.MessageType)
                {
                    case AMBMessageType.STATUS:
                        try
                        {
                            // Fetch the data as a AMB Status message
                            AMB.Status obj = new AMB.Status(returnObj.Data);

                            // Get and set the data from the message.
                            DecoderNoise = obj.Noise;
                            DecoderTemperature = obj.Temperature;
                            DecoderVoltage = obj.InputVoltage;

                            // Update time elapsed
                            UpdateTimeElapsed();

                            // TODO: Send to multicast thread.
                            //_mainWindow.SendToMulticastThread(heat, MulticastDataType.HeatObject);
                        }
                        catch (InvalidCastException)
                        {
                            // TODO: Implement logging
                            //MessageBox.Show(ex.Message, "Fel", MessageBoxButtons.OK, MessageBoxIcon.Error);
                        }
                        catch (Exception)
                        {
                            // TODO: Implement logging
                            //MessageBox.Show(ex.Message, "Fel", MessageBoxButtons.OK, MessageBoxIcon.Error);
                        }

                        break;

                    case AMBMessageType.PASSING:
                        try
                        {
                            // Fetch the data as a AMB Passing message
                            AMB.Passing obj = new AMB.Passing(returnObj.Data);

                            // Process the passing only if the timer is running.
                            if (obj != null && _dispatcherTimer.IsEnabled)
                                await ProcessPassing(obj);
                        }
                        catch (Exception ex)
                        {
                            WriteErrorToConsole(ex.Message);
                            // TODO: Implement logging
#if DEBUG
                            throw ex;
#endif
                        }
                        break;

                    case AMBMessageType.GET_TIME:
                        try
                        {
                            // Fetch the data as a AMB time message
                            AMB.GetTime obj = new AMB.GetTime(returnObj.Data);

                            // Check that the object contains the wanted data
                            if (obj != null && obj.RTCTime != null)
                            {
                                _decoderDifference = (DateTime.Now - obj.RTCTime);

                                if (!_isDecoderClockSynchronized)
                                {
                                    WriteToConsole(string.Format("Synkroniserar klockor... ({0})", _decoderDifference));
                                    _isDecoderClockSynchronized = true;
                                }

                                RunRace();
                            }
                        }
                        catch (Exception ex)
                        {
                            WriteErrorToConsole(ex.Message);
                            // TODO: Implement logging
#if DEBUG
                            throw ex;
#endif
                        }
                        break;
                }
            }
            catch (Exception ex)
            {
                // TODO: Implement logging

                WriteErrorToConsole(ex.Message);
#if DEBUG
                throw ex;
#endif
            }
        }

        /// <summary>
        /// Processes the passing. Checks if the passings transponder has a corresponding transponder in the collection of practicers/competitors,
        /// if not a new competitor is added with the corrresponding transponder.
        /// </summary>
        /// <param name="passing"></param>
        /// <returns></returns>
        protected async Task ProcessPassing(AMB.Passing passing)
        {
            try
            {
                // Check that the race has started and is not yet stopped or finished.
                if (!_dispatcherTimer.IsEnabled)
                    return;

                // Find the competitor in the collection
                HeatCompetitor competitor = this.Practisers.FirstOrDefault(p => p.TransponderId == passing.TransponderId);

                // Check if there was anything found
                if (competitor == null)
                {
                    // No competitor was found. Create a new with information from DB
                    Transponder transponder = null;

                    await DatabaseHelper.DoWithDBAsync<DBContext>(db =>
                    {
                        transponder = db.Transponders.FirstOrDefault(t => t.TransponderId == passing.TransponderId);

                        if (transponder != null)
                        {
                            if (transponder.Driver != null)
                            {
                                competitor = new HeatCompetitor(transponder.Driver, transponder.TransponderId);
                            }
                        }
                        else
                        {
                            competitor = new HeatCompetitor() { TransponderId = passing.TransponderId };
                        }
                    });

                    // Add the competitor to the collection
                    this.Practisers.Add(competitor);
                }

                if (competitor != null)
                {
                    // a competitor was found. Check that it has a collection of laps. If not, create one.
                    if (competitor.Laps == null)
                        competitor.Laps = new ObservableCollectionExt<Lap>();

                    // Check that the competitor has not finished.
                    if (!competitor.IsFinished)
                    {
                        if (_startDateTime.HasValue && _stopDateTime.HasValue)
                        {
                            //_competitors.Where(c => c.Transponder.Id == (int)passing.TransponderId).FirstOrDefault().AddLap(_startTimeStamp.Subtract(_decoderDifference), passing.PassingTime, passing.SignalStrength - DecoderNoise, passing.Voltage);
                            competitor.AddLap(_startDateTime.Value.Subtract(_decoderDifference), passing.PassingTime, passing.SignalStrength - DecoderNoise, passing.Voltage, passing.Hits);

                            if (passing.PassingTime.Add(_decoderDifference) >= _stopDateTime.Value)
                            {
                                //_competitors.Where(c => c.Transponder.Id == (int)passing.TransponderId).FirstOrDefault().IsFinished = true;

                                competitor.IsFinished = true;

                                //_competitors.Where(c => c.Transponder.Id == (int)passing.TransponderId).FirstOrDefault().FinishTime = (passing.PassingTime.Add(_decoderDifference) - _stopTimeStamp).TotalSeconds;

                                competitor.FinishTime = (passing.PassingTime.Add(_decoderDifference) - _stopDateTime.Value).TotalSeconds;

                            }
                        }
                    }
                }
                else
                {
                    // a competitor should be either created or found. Something must have gone wrong.
                    WriteErrorToConsole("Unable to process the passing!");
                }
            }
            catch (Exception ex)
            {
                // TODO: Log error
#if DEBUG
                throw ex;
#endif
            }
        }

        protected void PracticePage_Unloaded(object sender, RoutedEventArgs e)
        {
            this.Practisers = null;

            RaceGrid.DataContext = null;
            _decoderCommunicator.Dispose();
            _dispatcherTimer.Stop();
        }

        protected void PracticePage_Loaded(object sender, RoutedEventArgs e)
        {
            Dialogs.PreRaceDialog prd = new Dialogs.PreRaceDialog(this);
            prd.Owner = ParentMainWindow;
            prd.ShowDialog();

            lblTimer.Content = this.Duration.ToString(@"mm\:ss\.fff");

            // TODO: Send practice heat to multicast thread.
            //_mainWindow.SendToMulticastThread(heat, MulticastDataType.HeatObject);

            RaceGrid.DataContext = this.Practisers;

            _decoderDifference = new TimeSpan();

            _decoderCommunicator = new AMBCommunicator(Properties.Settings.Default.DecoderIPAddress, Properties.Settings.Default.DecoderTcpPort);
            _decoderCommunicator.WorkerReportsProgress = true;
            _decoderCommunicator.WorkerSupportsCancellation = true;
            _decoderCommunicator.ProgressChanged += _decoderCommunicator_ProgressChanged;
            _decoderCommunicator.RunWorkerCompleted += _decoderCommunicator_RunWorkerCompleted;

            _dispatcherTimer = new DispatcherTimer();
            _dispatcherTimer.Tick += _dispatcherTimer_Tick;
            _dispatcherTimer.Interval = new TimeSpan(0, 0, 0, 0, 10);

            _clockTimer = new DispatcherTimer();
            _clockTimer.Tick += _clockTimer_Tick;
            _clockTimer.Interval = new TimeSpan(0, 0, 1);
            _clockTimer.Start();
        }

        #region WriteToConsole

        public void WriteErrorToConsole(string exMessage)
        {
            Run item = new Run(exMessage) { Foreground = Brushes.Red };
            WriteToConsole(item);
        }

        public void WriteToConsole(string text)
        {
            WriteToConsole(new Run() { Text = text });
        }

        public void WriteToConsole(Run item)
        {
            tbConsole.Inlines.Add(string.Format("[{0}] ", DateTime.Now.ToString("HH:mm:ss")));
            tbConsole.Inlines.Add(item);
            tbConsole.Inlines.Add(Environment.NewLine);
            ScrollViewer scrollViewer = tbConsole.Parent as ScrollViewer;
            scrollViewer.ScrollToBottom();
        }

        public void WriteToConsole(List<Inline> items)
        {
            tbConsole.Inlines.Add(string.Format("[{0}] ", DateTime.Now.ToString("HH:mm:ss")));
            items.ForEach(i => tbConsole.Inlines.Add(i));
            tbConsole.Inlines.Add(Environment.NewLine);
        }

        #endregion

        #region Click events

        private void RaceGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            HeatCompetitor selectedCompetitor = ((DataGrid)sender).SelectedItem as HeatCompetitor;

            if (selectedCompetitor != null)
            {
                SelectedDriverInformation.Visibility = System.Windows.Visibility.Visible;
                SelectedDriverInformation.SetInformation(selectedCompetitor);
            }
        }

        private void btnStop_Click(object sender, RoutedEventArgs e)
        {

        }

        private void btnPause_Click(object sender, RoutedEventArgs e)
        {

        }

        private void btnStart_Click(object sender, RoutedEventArgs e)
        {
            StartRace();
        }

        private void btnPrint_Click(object sender, RoutedEventArgs e)
        {

        }

        #endregion

        private void StartRace()
        {
            if (!_decoderCommunicator.CancellationPending && !_dispatcherTimer.IsEnabled)
            {
                try
                {
                    TimeSpan durationLeft = this.Duration.Subtract(this.TimeElapsed);

                    this._stopDateTime = DateTime.Now.Add(durationLeft);

                    _decoderCommunicator.RunWorkerAsync(AMBCommand.GetTime());
                    WriteToConsole("Information: Kopplar upp mot decoder.");

                    btnStart.IsEnabled = false;
                    btnStop.IsEnabled = false;
                    btnPause.IsEnabled = true;

                    if (ParentMainWindow != null)
                        ParentMainWindow.SetAllNavigationButtons(false);
                }
                catch (Exception ex)
                {
                    // TODO: Log error
                    WriteErrorToConsole(string.Format("Intern fel: {0}", ex.Message));

                    btnStart.IsEnabled = true;
                    btnStop.IsEnabled = false;
                    btnPause.IsEnabled = false;

                    if (ParentMainWindow != null)
                        ParentMainWindow.SetAllNavigationButtons(true);
                }
            }
        }
    }
}
